#include "nr_net_client.h"

qint32 nr_net_request_data::g_seq_no = 0;

qint32 nr_net_request_data::generate_seq_no()
{
    return g_seq_no++;
}

nr_net_client::nr_net_client(QObject *parent)
    : QObject(parent)
    , m_is_login(false)
    , m_socket(NULL)
    , m_port(NR_NET_DEFAULT_PORT)
    , m_data_delegate(NULL)
{
}

nr_net_client::~nr_net_client()
{
}

nr_error_t nr_net_client::login(const QString& username, const QString& password, nr_net_reply_delegate *delegate)
{
    // Login the server and setup two sockets 
    // 1 for command socket
    // 2 for data socket : event or data
    if (m_is_login)
        return NR_ERROR_NONE;

    // Check the port
    if (m_port == NR_INVALID_PORT)
        return NR_ERROR_INVALID_PORT;

    Q_ASSERT(m_socket == NULL);

    // Create command socket
    m_socket = new nr_net_socket(new QTcpSocket(this));    
       
    bool ret = connect(m_socket, SIGNAL(net_connected(nr_net_socket*)), this, SLOT(on_net_connected(nr_net_socket*)));
    Q_ASSERT(ret);
    ret = connect(m_socket, SIGNAL(net_error(nr_net_socket*)), this, SLOT(on_net_error(nr_net_socket*)));
    Q_ASSERT(ret);
    ret = connect(m_socket, SIGNAL(net_ready_read(nr_net_socket*)), this, SLOT(on_net_receive_data(nr_net_socket*)));
    Q_ASSERT(ret);
    ret = connect(m_socket, SIGNAL(net_about_to_close(nr_net_socket*)), this, SLOT(on_net_about_to_close(nr_net_socket*)));
    Q_ASSERT(ret);
    
    nr_net_request_data *request_data = new nr_net_request_data(
        NR_NET_COMMAND_LOGIN, delegate);

    nr_request_login& request_login = request_data->request()->object_ref<nr_request_login>();
    request_login.username() = username;
    request_login.password() = password;
        
    push_request_data(request_data);

    m_socket->data() = request_data;
    m_socket->socket()->connectToHost(m_host, m_port);

    return NR_ERROR_NONE;
}

nr_error_t nr_net_client::logout(nr_net_reply_delegate *delegate)
{
    if (!m_is_login)
    {
        // Maybe login is in progress, close the socket anyway
        if (m_socket != NULL) 
        {
            delete m_socket;
            m_socket = NULL;
        }
        
        return NR_ERROR_NONE;
    }   
    
    nr_net_request_data *request_data = new nr_net_request_data(
        NR_NET_COMMAND_LOGOUT, delegate);

    push_request_data(request_data);
    
    m_is_login = false;

    // Send out the logout  
    return NR_ERROR_NONE;
}

nr_error_t nr_net_client::get_device_items(nr_net_reply_delegate *delegate)
{
    if (!m_is_login)
        return NR_ERROR_NOT_LOGIN;

    nr_net_request_data *request_data = new nr_net_request_data(
        NR_NET_COMMAND_GET_ITEMS, delegate);

    push_request_data(request_data);
    
    return send_request_data(*request_data);
}

nr_error_t nr_net_client::start_data(const nr_device_id_t& device_id, nr_net_reply_delegate *delegate)
{
    if (!m_is_login)
        return NR_ERROR_NOT_LOGIN;

    nr_net_request_data *request_data = new nr_net_request_data(
        NR_NET_COMMAND_START_DATA, delegate);

    nr_request_start_data& request = request_data->request()->object_ref<nr_request_start_data>();
    request.item_list().push_back(device_id);

    push_request_data(request_data);

    return send_request_data(*request_data);
}

nr_error_t nr_net_client::start_data(const QList<nr_device_id_t>& device_id_list, nr_net_reply_delegate *delegate)
{
    if (!m_is_login)
        return NR_ERROR_NOT_LOGIN;

    nr_net_request_data *request_data = new nr_net_request_data(
        NR_NET_COMMAND_START_DATA, delegate);

    nr_request_start_data* request = request_data->request()->object<nr_request_start_data>();
    request->item_list() = device_id_list;
    
    push_request_data(request_data);
    
    return send_request_data(*request_data);
}

nr_error_t nr_net_client::start_all_data(nr_net_reply_delegate *delegate)
{
    return NR_ERROR_NOT_IMPLEMENTED;
}

nr_error_t nr_net_client::stop_data(const nr_device_id_t& device_id, nr_net_reply_delegate *delegate)
{
    if (!m_is_login)
        return NR_ERROR_NOT_LOGIN;

    nr_net_request_data *request_data = new nr_net_request_data(
        NR_NET_COMMAND_STOP_DATA, delegate);

    nr_request_stop_data &request = request_data->request()->object_ref<nr_request_stop_data>();
    request.item_list().push_back(device_id);

    push_request_data(request_data);

    return send_request_data(*request_data);
}

nr_error_t nr_net_client::stop_data(const QList<nr_device_id_t>& device_id_list, nr_net_reply_delegate *delegate)
{
    if (!m_is_login)
        return NR_ERROR_NOT_LOGIN;

    nr_net_request_data *request_data = new nr_net_request_data(
        NR_NET_COMMAND_STOP_DATA, delegate);

    nr_request_start_data* request = request_data->request()->object<nr_request_start_data>();
    request->item_list() = device_id_list;

    push_request_data(request_data);

    return send_request_data(*request_data);
}

nr_error_t nr_net_client::stop_all_data(nr_net_reply_delegate *delegate)
{
    return NR_ERROR_NOT_IMPLEMENTED;
}

nr_error_t nr_net_client::query_data( const nr_device_id_t& device_id, QDateTime &begin_time, QDateTime &end_time, nr_net_reply_delegate *delegate )
{
    if (!m_is_login)
        return NR_ERROR_NOT_LOGIN;

    nr_net_request_data *request_data = new nr_net_request_data(
        NR_NET_COMMAND_QUERY_DATA, delegate);

    nr_request_query_data& request = request_data->request()->object_ref<nr_request_query_data>();
    request.id() = device_id;
    request.begin_time() = begin_time;
    request.end_time() = end_time;

    push_request_data(request_data);

    return send_request_data(*request_data);
}


void nr_net_client::on_net_connected(nr_net_socket* socket)
{
    // Send the login command
    nr_net_request_data* request_data = (nr_net_request_data*)socket->data();
    Q_ASSERT(request_data);
    
    send_request_data(*request_data);
}

void nr_net_client::on_net_data_connected(nr_net_socket* socket)
{
    // 

}

void nr_net_client::on_net_error(nr_net_socket*)
{

}

void nr_net_client::on_net_receive_data(nr_net_socket* socket)
{
    QByteArray byte_array;
    nr_error_t error;
    nr_net_request_data *request_data = NULL;
    // Read the replay header
    nr_net_command_header reply_header;
    quint64 read_len = socket->socket()->read((char*)&reply_header, sizeof(reply_header));
    if (read_len != sizeof(reply_header))
    {
        error = NR_ERROR_NETWORK_FAILED;
        goto Error;
    }
    
    // Check the reply header 
    if ((error = check_header(reply_header)) != NR_ERROR_NONE)
    {
        goto Error;
    }
    
    byte_array.resize(reply_header.content_length);
    read_len = socket->socket()->read(byte_array.data(), reply_header.content_length);
    if (read_len != reply_header.content_length)
    {
        error = NR_ERROR_NETWORK_FAILED;
        goto Error;
    }

    // Process the server data or event
    if (is_data_command(reply_header.cmd_type))
    {
        process_data(reply_header.cmd_type, byte_array);
        return;
    }

    // Find the request data in the request queue
    request_data = find_request_data_by_seq_no(reply_header.seq_no);
    if (request_data == NULL)
    {
        // Not existed, but should read over the existed command content

    }
    
    // Convert the byte array to object
    nr_net_helper::byte_array_to_object(byte_array, *request_data->reply());
    pre_process_reply_internal(reply_header, *request_data);

    if (request_data->delegate())
        request_data->delegate()->on_reply(*request_data->reply());
    
    // Remove it from the request queue
    remove_request_data_by_seq_no(reply_header.seq_no);
    return ;
    
Error:
    if (request_data)
    {
        pre_process_error_internal(*request_data, error);
        if (request_data->delegate())
            request_data->delegate()->on_error(error);
    }
    
    // Remove it from the request queue
    remove_request_data_by_seq_no(reply_header.seq_no);
}

nr_error_t nr_net_client::process_data(nr_net_command_type_t cmd_type, QByteArray &byte_array)
{
    if (cmd_type == NR_NET_COMMAND_SERVER_DATA)
    {
        nr_data *data = nr_data::create();
        nr_net_helper::byte_array_to_object(byte_array, *data);
        if(m_data_delegate)
            m_data_delegate->on_receive_data(data);

        nr_data::destroy(data);
    }
    else if (cmd_type == NR_NET_COMMAND_SERVER_EVENT)
    {
         nr_event *evt = nr_event::create();
         nr_net_helper::byte_array_to_object(byte_array, *evt);
         if (m_data_delegate)
             m_data_delegate->on_receive_event(evt);

         evt->dec_ref();
    }
    else
    {
        Q_ASSERT(false);    
    }

    return NR_ERROR_NONE;
}

void nr_net_client::on_net_about_to_close(nr_net_socket*)
{

}

void nr_net_client::on_net_timeout()
{

}

void nr_net_client::pre_process_reply_internal(nr_net_command_header &header, nr_net_request_data &request_data)
{
    if (request_data.command() == NR_NET_COMMAND_LOGIN)
    {
        nr_reply_login& reply_login = request_data.reply()->object_ref<nr_reply_login>();
        m_is_login = true;

        // Start the data socket ahead, but do nothing 
        m_seesion_id = header.session_id;
    }
    
}

void nr_net_client::pre_process_error_internal(nr_net_request_data &request_data, nr_error_t error)
{

}

nr_error_t nr_net_client::send_request_data(nr_net_request_data &request_data)
{
    Q_ASSERT(request_data.request());
    QByteArray request_byte_array = nr_net_helper::object_to_byte_array(*request_data.request());
    
    // Send out the header first 
    nr_net_command_header header;
    header.cmd_type = request_data.command();
    header.content_length = request_byte_array.size();
    header.seq_no = request_data.seq_no();
    header.session_id = m_seesion_id;

    // Send out the request 
    m_socket->socket()->write((char*)&header, sizeof(header));

    m_socket->socket()->write(request_byte_array.data(), request_byte_array.size());

    // Read the cmd result asynchronously 

    return NR_ERROR_NONE;
}

void nr_net_client::push_request_data(nr_net_request_data *request_data)
{
    Q_ASSERT(request_data);
    Q_ASSERT(!m_request_queue.contains(request_data->seq_no()));

    m_request_queue.insert(request_data->seq_no(), request_data);
}

nr_net_request_data* nr_net_client::find_request_data_by_seq_no(qint32 seq_no)
{
    nr_request_data_hash::iterator it =  m_request_queue.find(seq_no);
    if (it != m_request_queue.end())
        return it.value();
    else
        return NULL;
}

void nr_net_client::remove_request_data_by_seq_no(qint32 seq_no)
{
    nr_request_data_hash::iterator it =  m_request_queue.find(seq_no);
    if (it != m_request_queue.end())
    {
        delete it.value();
        m_request_queue.erase(it);
    }
}

bool nr_net_client::is_data_command(nr_net_command_type_t cmd_type)
{ 
    return (cmd_type == NR_NET_COMMAND_SERVER_DATA) ||
           (cmd_type == NR_NET_COMMAND_SERVER_EVENT);
}

